package cloud.tianai.mate.captcha.interceptor.impl;

import cloud.tianai.captcha.common.AnyMap;
import cloud.tianai.captcha.common.response.ApiResponse;
import cloud.tianai.captcha.common.response.CodeDefinition;
import cloud.tianai.captcha.common.util.CaptchaTypeClassifier;
import cloud.tianai.captcha.interceptor.Context;
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
import cloud.tianai.captcha.interceptor.CaptchaInterceptor;
import cloud.tianai.mate.captcha.interceptor.group.FeaturesCaptchaInterceptorGroup;
import cloud.tianai.mate.captcha.validator.common.util.TrackUtils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.TimeUnit;

import static cloud.tianai.captcha.validator.impl.SimpleImageCaptchaValidator.USER_CURRENT_PERCENTAGE_STD;

/**
 * @Author: 天爱有情
 * @date 2023/1/4 11:21
 * @Description 一定时间内差异校验, 基于IP校验， 必须是web应用， 且能获取到request
 */
@Slf4j
@RequiredArgsConstructor
public class DifferenceCaptchaInterceptor implements CaptchaInterceptor {
    public static final CodeDefinition DEFINITION = new CodeDefinition(60001, "diff check fail");

    @Setter
    @Getter
    /** 过期时间. */
    private Long timeWindow = TimeUnit.HOURS.toMillis(1);
    @Getter
    @Setter
    private String prefix = "captcha:valid:difference:";
    @Getter
    @Setter
    /** 检测在距离上一个验证码验证成功后的间隔时间. */
    private Long checkTimeInterval = 500L;
    @Getter
    @Setter
    /** 检测一定时间内验证次数超过一定数量后验证失败. */
    private Integer checkSuccessNum = 100;

    @Override
    public String getName() {
        return "difference_check";
    }

    @Override
    public ApiResponse<?> afterValid(Context context, String type, ImageCaptchaTrack imageCaptchaTrack, AnyMap validData, ApiResponse<?> basicValid) {
        if (!basicValid.isSuccess()) {
            return context.getGroup().afterValid(context, type, imageCaptchaTrack, validData, basicValid);
        }
        AnyMap featureMap = context.getData(FeaturesCaptchaInterceptorGroup.DATA_FEATURES, AnyMap.class);
        if (!CaptchaTypeClassifier.isSliderCaptcha(type) || featureMap == null) {
            return ApiResponse.ofSuccess();
        }
        List<Double> trackFeatures = context.getData(TrackFeaturesGenerator.TRACK_FEATURES_KEY, List.class);
        String key = context.getData(FeaturesCaptchaInterceptorGroup.DATA_FEATURES_KEY, String.class);
        if (trackFeatures == null) {
            trackFeatures = TrackUtils.features(imageCaptchaTrack.getTrackList());
            context.putData(TrackFeaturesGenerator.TRACK_FEATURES_KEY, trackFeatures);
        }
        long currentTimeMillis = System.currentTimeMillis();

        String percentageStd = validData.getString(USER_CURRENT_PERCENTAGE_STD, null);

        if (featureMap.containsKey("std") && percentageStd != null) {
            // 校验std
            Object std = featureMap.get("std");
            if (percentageStd.equals(std)) {
                log.warn("[验证码差异拦截器]在{}ms时间内，key:[{}]距离上次验证码std一致[type:{}, std:{}]，触发风控，进行拦截", timeWindow, key, type, std);
                context.end();
                return ApiResponse.ofMessage(DEFINITION);
            }
            if (CaptchaTypeClassifier.isSliderCaptcha(type)) {
                // 该差异校验只有在滑动类验证码才有效, 点选跳过
                Double xSameQuantityPercentage = Double.valueOf((String) featureMap.get("xSameQuantityPercentage"));
                Double ySameQuantityPercentage = Double.valueOf((String) featureMap.get("ySameQuantityPercentage"));
                Double tSameQuantityPercentage = Double.valueOf((String) featureMap.get("tSameQuantityPercentage"));
                if (trackFeatures.get(13).equals(xSameQuantityPercentage)) {
                    log.warn("[验证码差异拦截器]在{}ms时间内，key:[{}]距离上次验证码xSameQuantityPercentage一致[type:{}, xSameQuantityPercentage:{}]，触发风控，进行拦截", timeWindow, key, type, xSameQuantityPercentage);
                    context.end();
                    return ApiResponse.ofMessage(DEFINITION);
                }
                if (trackFeatures.get(14).equals(ySameQuantityPercentage)) {
                    log.warn("[验证码差异拦截器]在{}ms时间内，key:[{}]距离上次验证码ySameQuantityPercentage一致[type:{}, ySameQuantityPercentage:{}]，触发风控，进行拦截", timeWindow, key, type, ySameQuantityPercentage);
                    context.end();
                    return ApiResponse.ofMessage(DEFINITION);
                }
                if (trackFeatures.get(15).equals(tSameQuantityPercentage)) {
                    log.warn("[验证码差异拦截器]在{}ms时间内，key:[{}]tSameQuantityPercentage[type:{}, tSameQuantityPercentage:{}]，触发风控，进行拦截", timeWindow, key, type, tSameQuantityPercentage);
                    context.end();
                    return ApiResponse.ofMessage(DEFINITION);
                }
            }
            // 校验时间
            String preCheckTime = (String) featureMap.get("preCheckTime");
            if (Long.parseLong(preCheckTime) + checkTimeInterval > currentTimeMillis) {
                // 500毫秒内重复验证成功, 绝对不可能
                log.warn("[验证码差异拦截器]在{}ms时间内，key:[{}]距离上次验证成功不足{}ms，触发风控，进行拦截", timeWindow, key, checkTimeInterval);
                context.end();
                return ApiResponse.ofMessage(DEFINITION);
            }
            // 校验同一时间内验证次数异常
            String checkSuccessNumStr = (String) featureMap.get("checkSuccessNum");
            if (checkSuccessNumStr != null && Integer.parseInt(checkSuccessNumStr) > checkSuccessNum) {
                // 闲的没事老滑验证码干什么
                log.warn("[验证码差异拦截器]在{}ms时间内，key:[{}]共校验成功{}次，触发风控，进行拦截", timeWindow, key, checkSuccessNumStr);
                context.end();
                return ApiResponse.ofMessage(DEFINITION);
            }
        }
        // 全都存成字符串，防止某些原因导致序列化错误
        if (percentageStd != null) {
            featureMap.put("std", String.valueOf(percentageStd));
        }
        featureMap.put("xSameQuantityPercentage", String.valueOf(trackFeatures.get(13)));
        featureMap.put("ySameQuantityPercentage", String.valueOf(trackFeatures.get(14)));
        featureMap.put("tSameQuantityPercentage", String.valueOf(trackFeatures.get(15)));
        featureMap.put("preCheckTime", String.valueOf(currentTimeMillis));
        String checkSuccessNum = (String) featureMap.getOrDefault("checkSuccessNum", String.valueOf(0));
        featureMap.put("checkSuccessNum", String.valueOf(Integer.parseInt(checkSuccessNum) + 1));
        return ApiResponse.ofSuccess();
    }
}
